package com.hbu.product.hbumallproduct.service.impl;

import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.BooleanUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.TypeReference;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hbu.common.utils.PageUtils;
import com.hbu.common.utils.Query;
import com.hbu.common.utils.R;
import com.hbu.product.hbumallproduct.dao.CategoryDao;
import com.hbu.product.hbumallproduct.entity.CategoryEntity;
import com.hbu.product.hbumallproduct.event.CategoryDataSynchronizer;
import com.hbu.product.hbumallproduct.event.CategoryEvent;
import com.hbu.product.hbumallproduct.service.CategoryService;
import com.hbu.product.hbumallproduct.utils.ConnPool;
import com.hbu.product.hbumallproduct.utils.ConnectionContext;
import com.hbu.product.hbumallproduct.utils.SqlUtil;
import com.hbu.product.hbumallproduct.vo.Catelog2VO;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.jdbc.SQL;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author 90458
 */
@Service("categoryService")
@AllArgsConstructor
@Slf4j
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {

    private StringRedisTemplate redisTemplate;

    private ApplicationEventPublisher applicationEventPublisher;

    private ConnPool connPool;

    private ApplicationContext applicationContext;

    private RedissonClient redisson;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<CategoryEntity> page = this.page(
                new Query<CategoryEntity>().getPage(params),
                new QueryWrapper<CategoryEntity>()
        );

        return new PageUtils(page);
    }

    @Override
    public List<CategoryEntity> listWithTree() {
        List<CategoryEntity> categoryEntities = baseMapper.selectList(null);
        return categoryEntities.stream()
                .filter(category -> category.getParentCid() == 0)
                .peek(category -> category.setChild(getChildren(categoryEntities, category)))
                .sorted(Comparator.comparingInt(category -> (category.getSort() == null ? 0 : category.getSort())))
                .collect(Collectors.toList());


    }

    @Override
    public R removeMenusByIds(Long[] catIds) {
        // TODO: 2022/11/4 删除前要先判断菜单是否被引用
        return R.result(baseMapper.deleteBatchIds(Arrays.asList(catIds)), "删除");
    }

    @Override
    public Long[] findCategoryPath(Long catelogId) {
        ArrayList<Long> longs = new ArrayList<>();
        CategoryEntity categoryEntity = baseMapper.selectById(catelogId);
        longs.add(catelogId);
        while (categoryEntity.getParentCid() != 0) {
            longs.add(categoryEntity.getParentCid());
            categoryEntity = baseMapper.selectById(categoryEntity.getParentCid());
        }
        Collections.reverse(longs);
        return longs.toArray(new Long[0]);
    }

    @Override
/*    @Caching(evict = {
            @CacheEvict(value = "category", key = "'getFirstLevelCategorys'"),
            @CacheEvict(value = "category", key = "'getCatalogJson'")
    })*/
    @CacheEvict(value = "category", allEntries = true)
    @SneakyThrows
    public void updateDetail(CategoryEntity category) {
        Connection connection = connPool.getConnection();
        connection.setAutoCommit(false);
        SQL updateSql = new SQL().UPDATE("pms_category");
        SqlUtil.updateUtil(category, updateSql);
        updateSql.WHERE("cat_id = " + category.getCatId());
        log.debug("执行的sql语句为 : " + updateSql);
        PreparedStatement preparedStatement = connection.prepareStatement(updateSql.toString());
        preparedStatement.executeUpdate();
        preparedStatement.close();
        if (StringUtils.hasText(category.getName())) {
            ConnectionContext.setConnection(connection);
            int synchronizerNum = applicationContext.getBeanNamesForType(CategoryDataSynchronizer.class).length;
            CountDownLatch countDownLatch = new CountDownLatch(synchronizerNum);
            applicationEventPublisher.publishEvent(new CategoryEvent(this, category, countDownLatch));
            countDownLatch.await();
            ConnectionContext.clear();
        }
        connection.commit();
        connPool.free(connection);
    }

    @Override
    @Cacheable(value = "category", key = "#root.method.name")
    public List<CategoryEntity> getFirstLevelCategorys() {
        return baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", 0));
    }

    private List<CategoryEntity> getCategorysFromList(List<CategoryEntity> categoryEntities, Long parentCid) {
        return categoryEntities.stream().filter(category -> category.getParentCid().equals(parentCid)).collect(Collectors.toList());
    }

    @Override
    @Cacheable(value = "category", key = "#root.method.name")
    public Map<String, List<Catelog2VO>> getCatalogJson() {
        List<CategoryEntity> categoryEntities = baseMapper.selectList(null);
        List<CategoryEntity> firstLevelCategorys = getCategorysFromList(categoryEntities, 0L);
        return firstLevelCategorys.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
            List<CategoryEntity> secondLevelCategorys = getCategorysFromList(categoryEntities, v.getCatId());
            return secondLevelCategorys.stream().map(secondLevelCategory -> {
                Catelog2VO catelog2VO = new Catelog2VO(v.getCatId().toString(), null, secondLevelCategory.getCatId().toString(), secondLevelCategory.getName());
                List<CategoryEntity> thirdLevelCategorys = getCategorysFromList(categoryEntities, secondLevelCategory.getCatId());
                List<Catelog2VO.Catelog3VO> catalog3List = thirdLevelCategorys.stream().map(thirdLevelCategory -> new Catelog2VO.Catelog3VO(secondLevelCategory.getCatId().toString(), thirdLevelCategory.getCatId().toString(), thirdLevelCategory.getName())).collect(Collectors.toList());
                catelog2VO.setCatalog3List(catalog3List);
                return catelog2VO;
            }).collect(Collectors.toList());
        }));
    }

    public Map<String, List<Catelog2VO>> getCatalogJsonByHand() {
        String catalogJSON = redisTemplate.opsForValue().get("catalogJSON");
        Map<String, List<Catelog2VO>> catalogJsonFromDb;
        if (StringUtils.hasText(catalogJSON)) {
            catalogJsonFromDb = JSON.parseObject(catalogJSON, new TypeReference<Map<String, List<Catelog2VO>>>() {
            });
        } else {
            catalogJsonFromDb = getLockFromRedisson();
        }
        return catalogJsonFromDb;
    }

    private Map<String, List<Catelog2VO>> getLockFromRedisson() {
        RLock lock = redisson.getLock("catalogJson-lock");
        lock.lock();
        return getCatalogJsonFromDb();
    }

    @SneakyThrows
    private Map<String, List<Catelog2VO>> getLockFromCache() {
        String uuid = UUID.randomUUID().toString();
        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 30, TimeUnit.SECONDS);
        if (BooleanUtil.isTrue(lock)) {
            // 加锁成功，执行业务
            Map<String, List<Catelog2VO>> catalogJson = getCatalogJsonFromDb();
/*          通过lua脚本的方式将解锁操作变为原子
            String lockValue = redisTemplate.opsForValue().get("lock");
            if (uuid.equals(lockValue)) {
                redisTemplate.delete("lock");
            }*/
            String luaScript = "            if redis.call('get',KEYS[1]) == ARGV[1]\n" +
                    "            then\n" +
                    "                return redis.call('del',KEYS[1])\n" +
                    "            else\n" +
                    "                return 0\n" +
                    "            end";
            Integer luaResponse = redisTemplate.execute(new DefaultRedisScript<>(luaScript, Integer.class),
                    Collections.singletonList("lock"), uuid);
            return catalogJson;
        } else {
            // 加锁失败，自旋
            Thread.sleep(100);
            return getLockFromCache();
        }
    }

    private Map<String, List<Catelog2VO>> getCatalogJsonFromDb() {
        String catalogJSON = redisTemplate.opsForValue().get("catalogJSON");
        // 抢到锁之后再做一次判断，如果此时redis中已经有了，直接将结果返回。
        if (StringUtils.hasText(catalogJSON)) {
            return JSON.parseObject(catalogJSON, new TypeReference<Map<String, List<Catelog2VO>>>() {
            });
        }
        List<CategoryEntity> categoryEntities = baseMapper.selectList(null);
        List<CategoryEntity> firstLevelCategorys = getCategorysFromList(categoryEntities, 0L);
        Map<String, List<Catelog2VO>> catalogJsonFromDb = firstLevelCategorys.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
            List<CategoryEntity> secondLevelCategorys = getCategorysFromList(categoryEntities, v.getCatId());
            return secondLevelCategorys.stream().map(secondLevelCategory -> {
                Catelog2VO catelog2VO = new Catelog2VO(v.getCatId().toString(), null, secondLevelCategory.getCatId().toString(), secondLevelCategory.getName());
                List<CategoryEntity> thirdLevelCategorys = getCategorysFromList(categoryEntities, secondLevelCategory.getCatId());
                List<Catelog2VO.Catelog3VO> catalog3List = thirdLevelCategorys.stream().map(thirdLevelCategory -> new Catelog2VO.Catelog3VO(secondLevelCategory.getCatId().toString(), thirdLevelCategory.getCatId().toString(), thirdLevelCategory.getName())).collect(Collectors.toList());
                catelog2VO.setCatalog3List(catalog3List);
                return catelog2VO;
            }).collect(Collectors.toList());
        }));
        // 先将结果放入redis后，再释放锁
        redisTemplate.opsForValue().set("catalogJSON", JSON.toJSONString(catalogJsonFromDb));
        return catalogJsonFromDb;
    }

    private List<CategoryEntity> getChildren(List<CategoryEntity> categoryEntities, CategoryEntity root) {
        return categoryEntities.stream()
                .filter(iter -> iter.getParentCid().equals(root.getCatId()))
                .peek(category -> category.setChild(getChildren(categoryEntities, category)))
                .sorted(Comparator.comparingInt(category -> (category.getSort() == null ? 0 : category.getSort())))
                .collect(Collectors.toList());
    }

}